﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;

namespace OSM
{
	public enum OSMType
	{
		Unknown,
		AdminBorder,
		Way,
		CoastLine
	}

	public class OSMData
	{
		public double BoundMinX, BoundMinY, BoundMaxX, BoundMaxY;

		public Dictionary<Int64, OSMNode> NodeID = new Dictionary<long, OSMNode>();
		public Dictionary<Int64, OSMWay> WayID = new Dictionary<long, OSMWay>();
		public Dictionary<Int64, OSMRelation> RelID = new Dictionary<long, OSMRelation>();

		//public List<OSMNode> Nodes = new List<OSMNode>();
		//public List<OSMWay> Ways = new List<OSMWay>();
		//public List<OSMRelation> Relations = new List<OSMRelation>();

		public void ClearAll()
		{
			NodeID.Clear();
			WayID.Clear();
			RelID.Clear();
		}
		public void BuildLink()
		{
			foreach (var x in RelID.Values)
			{
				x.admin_centre = null;
				x.Subareas = null;
				x.Borders.Clear();
				x.hasborder = false;
			}
			foreach (var l in WayID.Values)
			{
				l.Border_Member.Clear();
				l.NodeObj.Clear();
			}
			foreach (var n in NodeID.Values)
			{
				n.admin_border = null;
			}
			foreach (OSMRelation R in RelID.Values)
			{
				List<OSMRelation> subs = new List<OSMRelation>();
				foreach (OSMMember M in R.Members)
				{
					if (M.Type == "node" && M.Role == "admin_centre")
					{
						R._admin_centre = M.Ref;
						if (NodeID.ContainsKey(M.Ref))
						{
							M.RefObj = R.admin_centre = NodeID[M.Ref];
							(M.RefObj as OSMNode).AddBorder(R);
						}
					}
					else if (M.Type == "way")
					{
						if (WayID.ContainsKey(M.Ref))
						{
							OSMWay w = WayID[M.Ref];
							M.RefObj = w;
							w.Border_Member.Add(R);
						}
					}
					else if (M.Role == "subarea")
					{
						if (RelID.ContainsKey(M.Ref))
						{
							OSMRelation r = RelID[M.Ref];
							M.RefObj = r;
							subs.Add(r);
						}
					}
				}
				if (subs.Count == 0)
					R.Subareas = null;
				else
				{
					R.Subareas = subs.ToArray();
					foreach (var sub in R.Subareas)
						sub.Parent = R;
				}
			}
			foreach (OSMWay W in WayID.Values)
			{
				W.NodeObj.Clear();
				for (int i = 0; i < W.Nodes.Count; i++)
					W.NodeObj.Add(NodeID[W.Nodes[i]]);
			}
		}

		const int BUFFER_LENGTH = 1024 * 1024 * 16;
		public static List<Tuple<long, long>> GetRowCnt(String FN, Action<long> prg)
		{
			List<Tuple<long, long>> res = new List<Tuple<long, long>>();
			long N = 0;
			int M = 0;
			long S = new FileInfo(FN).Length;
			FileStream FS = File.OpenRead(FN);
			byte[] buf = new byte[BUFFER_LENGTH];
			long Offset = 0;
			while (N < S)
			{
				int b = FS.Read(buf, 0, BUFFER_LENGTH);
				for (int i = 0; i < b; i++)
				{
					if (buf[i] == 0x0A)
					{
						M++;
						if (M % 100000 == 0)
						{
							res.Add(new Tuple<long, long>(Offset, M));
							Offset = N + i + 1;
						}
					}
				}
				N += b;
				prg(N * 100 / S);
			}
			res.Add(new Tuple<long, long>(N, M));
			FS.Close();
			return res;
		}

		static void WriteTag(TextWriter W, Int64 id, string L)
		{
			string[] row = L.Split('"');
			W.WriteLine(id + "\t\"" + row[1] + "\"\t\"" + row[3] + "\"");
		}

		public static void ConvertOSM(String Code, String FN, String Dir, string label, Action<int[]> A)
		{
			BinaryReader R = new BinaryReader(File.OpenRead(FN));
			BinaryWriter nodes = new BinaryWriter(File.Create(Dir + "nodes.bin"));
			TextWriter node_tags = File.CreateText(Dir + "node_tags.bin");

			BinaryWriter ways = new BinaryWriter(File.Create(Dir + "ways.bin"));
			BinaryWriter way_nodes = new BinaryWriter(File.Create(Dir + "way_nodes.bin"));
			TextWriter way_tags = File.CreateText(Dir + "way_tags.bin");

			BinaryWriter rels = new BinaryWriter(File.Create(Dir + "relations.bin"));
			TextWriter rel_members = File.CreateText(Dir + "relation_members.bin");
			TextWriter rel_tags = File.CreateText(Dir + "relation_tags.bin");

			DateTime importtime = DateTime.Now;
			List<Tuple<long, long>> NRows = new List<Tuple<long, long>>();
			if (File.Exists(Code + ".idx"))
			{
				string[] row = File.ReadAllLines(Code + ".idx");
				foreach (var r in row)
				{
					NRows.Add(new Tuple<long, long>(long.Parse(r.Split(' ')[0]), long.Parse(r.Split(' ')[1])));
				}
			}
			else
				NRows = GetRowCnt(FN, x =>
					{
					});
			long NRow = NRows.Last().Item2;
			//int NRow = NRows.Count;

			long CurrentNode = -1, CurrentWay = -1, CurrentRel = -1;
			int NNode = 0, NWay = 0, NRel = 0;
			bool bound = false;
			long NLine = 0;
			int[] NL = new int[] { 0, 0, 0, 0 };
			for (int i = 0; i < NRows.Count - 1; i++)
			{
				long S_Start = NRows[i].Item1;
				long S_End = NRows[i + 1].Item1;
				byte[] buf = R.ReadBytes((int)(S_End - S_Start));

				int N = 0;
				int LastRow = 0;
				while (N < buf.Length)
				{
					if (buf[N] == 0x0A)
					{
						String L = Encoding.Default.GetString(buf, LastRow, N - LastRow);
						LastRow = N + 1;
						N++;
						{
							L = L.Trim();
							#region boundary
							if (L.StartsWith("<bounds"))
							{
								bound = true;
								String[] row = L.Split('"');
								double BoundMinX = double.Parse(row[3]);
								double BoundMinY = double.Parse(row[1]);
								double BoundMaxX = double.Parse(row[7]);
								double BoundMaxY = double.Parse(row[5]);
							}
							#endregion
							#region nodes
							if (CurrentNode != -1)
							{
								if (L == "</node>")
									CurrentNode = -1;
								else if (L.StartsWith("<tag "))
								{
									WriteTag(node_tags, CurrentNode, L);
								}
							}
							else if (L.StartsWith("<node"))
							{
								String[] row = L.Split('"');
								double lng = double.Parse(Regex.Match(L, "lon=\\\"(-?[0-9\\.]*)\\\"", RegexOptions.Singleline).Groups[1].Value);
								double lat = double.Parse(Regex.Match(L, "lat=\\\"(-?[0-9\\.]*)\\\"", RegexOptions.Singleline).Groups[1].Value);

								#region bound
								if (!bound)
								{
									if (NNode == 0)
									{
										//BoundMinX = BoundMaxX = lng;
										//BoundMinY = BoundMaxY = lat;
									}
									else
									{
										//BoundMinX = Math.Min(BoundMinX, lng);
										//BoundMinY = Math.Min(BoundMinY, lat);
										//BoundMaxX = Math.Max(BoundMaxX, lng);
										//BoundMaxY = Math.Max(BoundMaxY, lat);
									}
								}
								#endregion
								CurrentNode = Int64.Parse(row[1]);
								nodes.Write(CurrentNode);
								nodes.Write(lng);
								nodes.Write(lat);
								NNode++;
								if (L.EndsWith("/>"))
									CurrentNode = -1;
							}
							#endregion
							#region ways
							else if (CurrentWay != -1)
							{
								if (L == "</way>")
									CurrentWay = -1;
								else if (L.StartsWith("<nd "))
								{
									String[] row = L.Split('"');
									Int64 waynode = Int64.Parse(row[1]);
									way_nodes.Write(CurrentWay);
									way_nodes.Write(waynode);
								}
								else if (L.StartsWith("<tag "))
								{
									WriteTag(way_tags, CurrentWay, L);
								}
							}
							else if (L.StartsWith("<way "))
							{
								String[] row = L.Split('"');
								CurrentWay = Int64.Parse(row[1]);
								NWay++;
								ways.Write(CurrentWay);
								if (L.EndsWith("/>"))
									CurrentWay = -1;
							}
							#endregion
							#region relations
							else if (CurrentRel != -1)
							{
								if (L == "</relation>")
									CurrentRel = -1;
								else if (L.StartsWith("<tag "))
								{
									WriteTag(rel_tags, CurrentRel, L);
								}
								else if (L.StartsWith("<member "))
								{
									String[] row = L.Split('"');
									rel_members.WriteLine(CurrentRel + "\t\"" + row[1] + "\"\t" + row[3] + "\t\"" + row[5] + "\"");
									//OSMMember Res = CurrentRel.AddMember(row[1], Int64.Parse(row[3]), row[5]);
								}
							}
							else if (L.StartsWith("<relation "))
							{
								String[] row = L.Split('"');
								CurrentRel = Int64.Parse(row[1]);
								rels.Write(CurrentRel);
								NRel++;
								if (L.EndsWith("/>"))
									CurrentRel = -1;
							}
							#endregion
							NLine++;
							TimeSpan it = new TimeSpan(0, 0, 0, 0, 500);
							if (A != null && (DateTime.Now - importtime > it || NLine == NRow))
							{
								importtime = DateTime.Now;
								NL[0] = (int)((NLine * 1000) / NRow);
								NL[1] = NNode;
								NL[2] = NWay;
								NL[3] = NRel;
								A(NL);
							}
						}
					}
					N++;
				}
			}
			R.Close();
			nodes.Close();
			node_tags.Close();

			ways.Close();
			way_nodes.Close();
			way_tags.Close();

			rels.Close();
			rel_members.Close();
			rel_tags.Close();
		}

		public void AssignAdminCentre()
		{
			foreach (var X in RelID.Values)
			{
				if (X._admin_centre != -1 && NodeID.ContainsKey(X._admin_centre))
					X.admin_centre = NodeID[X._admin_centre];
			}
		}

		const int NodeSize = 24;
		const int WaySize = 8;
		const int RelSize = 12;
		const int RelMSize = 16;

		public static void SaveTags<OSM>(Dictionary<Int64, OSM> Data, String FN) where OSM : OSMObj
		{
			TextWriter W = File.CreateText(FN);
			foreach (var X in Data)
				foreach (var t in X.Value.Tags)
					W.WriteLine(X.Key + "\t\"" + t.Key + "\"\t\"" + t.Value + "\"");
			W.Close();
		}
		public static void LoadTags<OSM>(Dictionary<Int64, OSM> Data, String FN) where OSM : OSMObj
		{
			TextReader R = File.OpenText(FN);
			String L = null;
			while ((L = R.ReadLine()) != null)
			{
				String[] row = L.Split('\t', '"');
				Int64 ID = Int64.Parse(row[0]);
				if (Data.ContainsKey(ID) && !Data[ID].Tags.ContainsKey(row[2]))
					Data[ID].Tags.Add(row[2], row[5]);
			}
			R.Close();
		}

		public void Save(String Dir = OSMCore.OSMPath+"temp\\")
		{
			SaveNodes(Dir);
			SaveWays(Dir);
			SaveRelations(Dir);
		}
		void SaveNodes(String Dir)
		{
			BinaryWriter W = new BinaryWriter(File.Create(Dir + "nodes.bin"));
			byte[] Buf = new byte[NodeSize];
			foreach (OSMNode N in NodeID.Values)
			{
				//if (i != Nodes[i].Cnt)
				//	throw new Exception("Offset integrity error!");
				Buffer.BlockCopy(BitConverter.GetBytes(N.ID), 0, Buf, 0, 8);
				Buffer.BlockCopy(BitConverter.GetBytes(N.Longit), 0, Buf, 8, 8);
				Buffer.BlockCopy(BitConverter.GetBytes(N.Latit), 0, Buf, 16, 8);
				W.Write(Buf);
			}
			W.Close();
			//File.WriteAllBytes(FN + ".bin", Buf);
			SaveTags(NodeID, Dir + "node_tags.bin");
		}
		void SaveWays(String Dir)
		{
			BinaryWriter Wr = new BinaryWriter(File.Create(Dir + "ways.bin"));
			int WayNodeCount = 0;
			byte[] Buf = new byte[WayID.Count * 0 + 1 * WaySize];
			int i = 0;
			foreach (OSMWay NN in WayID.Values)
			{
				//if (i != Nodes[i].Cnt)
				//	throw new Exception("Offset integrity error!");
				Buffer.BlockCopy(BitConverter.GetBytes(NN.ID), 0, Buf, 0, 8);
				Wr.Write(Buf);
			}
			//foreach (OSMWay W in WayID.Values)
			//{
			//	Buffer.BlockCopy(BitConverter.GetBytes(W.ID), 0, Buf, i * WaySize, 8);
			//	WayNodeCount += W.Nodes.Count;
			//	i++;
			//}
			//File.WriteAllBytes(FN + ".bin", Buf);
			Wr.Close();
			//SaveTags(WayID, Dir + "way_tags.bin");
			Wr = new BinaryWriter(File.Create(Dir + "way_nodes.bin"));
			foreach (OSMWay W in WayID.Values)
			{
				foreach (long N in W.Nodes)
				{
					Wr.Write(W.ID);
					Wr.Write(N);
				}
			}
			Wr.Close();
			SaveTags(WayID, Dir + "way_tags.bin");
		}
		void SaveRelations(String Dir)
		{
			byte[] Buf = new byte[RelID.Count * 8];
			int i = 0;
			foreach (OSMRelation Rel in RelID.Values)
			//for (int i = 0; i < Relations.Count; i++)
			{
				Buffer.BlockCopy(BitConverter.GetBytes(Rel.ID), 0, Buf, i * 8, 8);
				i++;
			}
			File.WriteAllBytes(Dir + "relations.bin", Buf);
			SaveTags(RelID, Dir + "relation_tags.bin");
			TextWriter W = File.CreateText(Dir + "relation_members.bin");
			foreach (OSMRelation R in RelID.Values)
			{
				foreach (OSMMember M in R.Members)
					W.WriteLine(R.ID + "\t\"" + M.Type + "\"\t" + M.Ref + "\t\"" + M.Role + "\"");
			}
			W.Close();
		}

		public void Load(String Dir = OSMCore.OSMPath+"temp\\")
		{
			LoadRelations(Dir, true, false);
			LoadWays(Dir, true, false);
			LoadNodes(Dir, false);
			BuildLink();
		}
		public void Merge(String Dir = OSMCore.OSMPath+"temp\\")
		{
			LoadRelations(Dir, true, true);
			LoadWays(Dir, true, true);
			LoadNodes(Dir, true);
			BuildLink();
		}

		public void ReloadNodes(String Dir)
		{
			String FN1 = Dir + "nodes.bin";
			FileInfo FI = new FileInfo(FN1);
			long FS = FI.Length;
			BinaryReader R = new BinaryReader(File.OpenRead(FN1));
			for (int i = 0; i < FS; i += 24)
			{
				Int64 id = R.ReadInt64();
				double lng = R.ReadDouble();
				double lat = R.ReadDouble();
				if (NodeID.ContainsKey(id))
				{
					NodeID[id].Longit = lng;
					NodeID[id].Latit = lat;
				}
			}
			R.Close();
			String FN2 = Dir + "node_tags.bin";
			LoadTags(NodeID, FN2);
		}
		#region Unique adding
		public void AddNodeC(Int64 id, double lng, double lat)
		{
			if (!NodeID.ContainsKey(id))
				NodeID.Add(id, new OSMNode(id, lng, lat));
		}
		public void AddNodeC(Int64 id)
		{
			AddNodeC(id, double.NaN, double.NaN);
		}
		public void AddWayC(Int64 id)
		{
			if (!WayID.ContainsKey(id))
				WayID.Add(id, new OSMWay(id));
		}
		public void AddRelationC(Int64 id)
		{
			if (!RelID.ContainsKey(id))
				RelID.Add(id, new OSMRelation(id));
		}
		#endregion

		void LoadNodes(String Dir, bool merge)
		{
			String FN1 = Dir + "nodes.bin";
			FileInfo FI = new FileInfo(FN1);
			long FS = FI.Length;
			if (!merge)
			{
				NodeID.Clear();
				NodeID = new Dictionary<long, OSMNode>((int)(FS / 24));
			}
			BinaryReader R = new BinaryReader(File.OpenRead(FN1));
			for (int i = 0; i < FS; i += 24)
			{
				Int64 id = R.ReadInt64();
				double lng = R.ReadDouble();
				double lat = R.ReadDouble();
				AddNodeC(id, lng, lat);
			}
			R.Close();
			String FN2 = Dir + "node_tags.bin";
			LoadTags(NodeID, FN2);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="Dir">Source path</param>
		/// <param name="loadroot">reload root entries</param>
		/// <param name="merge">Merge with existing entries</param>
		void LoadWays(String Dir, bool loadroot, bool merge)
		{
			String FN1 = Dir + "ways.bin";
			String FN3 = Dir + "way_tags.bin";
			FileInfo FI;
			long FS;
			BinaryReader R;
			if (loadroot)
			{
				if (!merge)
					WayID.Clear();
				FI = new FileInfo(FN1);
				FS = FI.Length;
				R = new BinaryReader(File.OpenRead(FN1));
				for (long i = 0; i < FS; i += 8)
				{
					long id = R.ReadInt64();
					AddWayC(id);
				}
				R.Close();
			}
			LoadWayNodes(Dir, merge);
			LoadTags(WayID, FN3);
		}
		public void LoadWayNodes(String Dir, bool merge)
		{
			if (!merge)
				foreach (var w in WayID.Values)
					w.Nodes.Clear();
			String FN = Dir + "way_nodes.bin";
			FileInfo FI = new FileInfo(FN);
			long FS = FI.Length;
			BinaryReader R = new BinaryReader(File.OpenRead(FN));
			for (long N = 0; N < FS; N += 16)
			{
				Int64 way = R.ReadInt64();
				Int64 node = R.ReadInt64();
				if (WayID.ContainsKey(way))
					WayID[way].Nodes.Add(node);
			}
		}

		/// <summary>
		/// Loads relations
		/// </summary>
		/// <param name="Dir">Source path</param>
		/// <param name="loadroot">Force load root entries</param>
		/// <param name="merge">Merge with existing entries</param>
		public void LoadRelations(String Dir, bool loadroot, bool merge)
		{
			String FN1 = Dir + "relations.bin";
			String FN3 = Dir + "relation_tags.bin";

			if (loadroot)
			{
				if (!merge)
					RelID.Clear();
				byte[] Buf = File.ReadAllBytes(FN1);
				for (int N = 0; N < Buf.Length; )
				{
					Int64 id = BitConverter.ToInt64(Buf, N);
					N += 8;
					AddRelationC(id);
				}
				Buf = null;
			}
			LoadRelationMembers(Dir, merge);
			LoadTags(RelID, FN3);
		}
		public void LoadRelationMembers(String Dir, bool merge)
		{
			if (!merge)
				foreach (var r in RelID.Values)
					r.Members.Clear();
			String FN = Dir + "relation_members.bin";
			TextReader T = File.OpenText(FN);
			String L = "";
			while ((L = T.ReadLine()) != null)
			{
				Int64 ID = Int64.Parse(L.Split('\t')[0]);
				if (RelID.ContainsKey(ID))
				{
					OSMRelation r = RelID[ID];
					String[] row = L.Split('\t', '"');
					r.AddMember(row[2], Int64.Parse(row[4]), row[6]);
				}
			}
			T.Close();
		}

		public void AddNodeR(OSMNode N)
		{
			if (!NodeID.ContainsKey(N.ID))
				NodeID.Add(N.ID, N);
		}
		public void AddWayR(OSMWay W)
		{
			if (!WayID.ContainsKey(W.ID))
				WayID.Add(W.ID, W);
			foreach (OSMNode N in W.NodeObj)
				AddNodeR(N);
		}
		public void AddRegionR(OSMRelation R, bool addsubregion = false)
		{
			if (!RelID.ContainsKey(R.ID))
			{
				RelID.Add(R.ID, R);
				foreach (OSMMember M in R.Members)
					if (M.RefObj is OSMWay)
						AddWayR(M.RefObj as OSMWay);
				if (addsubregion && R.Subareas != null)
				{
					foreach (var sub in R.Subareas)
						AddRegionR(sub, true);
				}
			}
		}

		#region Data Query
		public void GatherVillages()
		{
			foreach (OSMNode N in NodeID.Values)
			{
				if (N.admin_border.Count > 0)
				{
					Console.WriteLine("Node #" + N.ID + " Is center for following:");
					foreach (var b in N.admin_border)
						Console.WriteLine(b);
				}
			}
		}
		public OSMNode FetchCity(String Name)
		{
			return null;
		}
		#endregion
	}

	public interface IOSMObj
	{
		Int64 ID
		{
			get;
			set;
		}
	}
	public class OSMObj : IOSMObj
	{
		public Dictionary<String, String> Tags = new Dictionary<string, string>();
		public String this[String Key]
		{
			get
			{
				if (Tags.ContainsKey(Key))
					return Tags[Key];
				return null;
			}
			set
			{
				if (Tags.ContainsKey(value))
					Tags[Key] = value;
				else
					Tags.Add(Key, value);
			}
		}
		Int64 _ID;
		public Int64 ID
		{
			get
			{
				return _ID;
			}
			set
			{
				_ID = value;
			}
		}
		public OSMObj(Int64 id)
		{
			ID = id;
		}

		public void AddTag(String K, String V)
		{
			Tags.Add(K, V);
		}
		public void FetchTag(String L)
		{
			string[] row = L.Split('"');
			Tags.Add(row[1], row[3]);
		}
	}
	public class OSMNode : OSMObj
	{
		public override string ToString()
		{
			return "Node #" + ID;
		}
		public double Longit, Latit;
		public V3G ToV3G()
		{
			return new V3G(Longit, Latit);
		}
		public List<OSMRelation> admin_border;

		public List<OSMWay> Way_Member = new List<OSMWay>();
		public void Include(OSMWay W)
		{
			Way_Member.Add(W);
		}
		public void Exclude(OSMWay W)
		{
			W.NodeObj.Remove(this);
			W.Nodes.Remove(ID);
			Way_Member.Remove(W);
		}
		public void AddBorder(OSMRelation r)
		{
			if (admin_border == null)
				admin_border = new List<OSMRelation>();
			admin_border.Add(r);
		}
		public void SortAdmin()
		{
			admin_border.Sort((OSMRelation X1, OSMRelation X2) => -X1["admin_level"].CompareTo(X2["admin_level"]));
		}
		public OSMNode(Int64 id, double lng, double lat)
			: base(id)
		{
			Longit = lng;
			Latit = lat;
		}
		public bool PosEq(OSMNode N)
		{
			return Longit == N.Longit && Latit == N.Latit;
		}
	}
	public class OSMWay : OSMObj
	{
		public override string ToString()
		{
			return "Way #" + ID;
		}
		public List<Int64> Nodes = new List<Int64>();
		public List<OSMNode> NodeObj = new List<OSMNode>();

		public OSMNode FirstNode
		{
			get
			{
				return NodeObj[0];
			}
		}
		public OSMNode LastNode
		{
			get
			{
				return NodeObj[NodeObj.Count - 1];
			}
		}

		public List<OSMRelation> Border_Member = new List<OSMRelation>();
		public OSMWay(Int64 id)
			: base(id)
		{
		}
		public void AddNode(Int64 N)
		{
			Nodes.Add(N);
		}
		public void AddNode(OSMNode N)
		{
			NodeObj.Add(N);
			Nodes.Add(N.ID);
		}
	}
	public class OSMBorder
	{
		public List<OSMWay> Ways;
		public List<bool> Dirs;

		public Poly3G BorderPoly;

		public void MakePoly()
		{
			List<V3G> input = new List<V3G>();
			for (int i = 0; i < Ways.Count; i++)
			{
				int N1 = Dirs[i] ? Ways[i].Nodes.Count - 1 : 0;
				int N2 = Dirs[i] ? 1 : Ways[i].Nodes.Count - 2;
				for (int j = N1; j != N2 + (Dirs[i] ? -1 : 1); j += Dirs[i] ? -1 : 1)
					input.Add(new V3G(Ways[i].NodeObj[j].Longit, Ways[i].NodeObj[j].Latit));
			}
			BorderPoly = new Poly3G(input.ToArray());
		}
		public OSMBorder()
		{
			Ways = new List<OSMWay>();
			Dirs = new List<bool>();
		}
		public OSMNode FirstNode
		{
			get
			{
				if (Ways.Count == 0 || Ways[0].Nodes.Count == 0)
				{
				}
				return Dirs[0] ? Ways[0].LastNode : Ways[0].FirstNode;
			}
		}
		public OSMNode LastNode
		{
			get
			{
				return Dirs.Last() ? Ways.Last().FirstNode : Ways.Last().LastNode;
			}
		}
		public bool IsClosed()
		{
			if (Ways.Count == 1)
			{
			}
			return IsConsistent() && FirstNode == LastNode;
		}
		public bool IsConsistent()
		{
			if (Ways.Count < 2)
				return true;
			for (int i = Ways.Count - 2; i < Ways.Count - 1; i++)
			{
				OSMNode N1 = Dirs[i] ? Ways[i].FirstNode : Ways[i].LastNode;
				OSMNode N2 = Dirs[i + 1] ? Ways[i + 1].LastNode : Ways[i + 1].FirstNode;
				if (N1 != N2)
				{
					if (N1.ID == N2.ID)
						throw new Exception("Multiple Instance");
					return false;
				}
			}
			return true;
		}
		public void AddWay(OSMWay w, bool d)
		{
			Ways.Add(w);
			Dirs.Add(d);
		}
		public void RemoveLast()
		{
			Dirs.RemoveAt(Ways.Count - 1);
			Ways.RemoveAt(Ways.Count - 1);
		}

		public void SearchBorder(List<OSMWay> input)
		{
		}
	}

	public class OSMMember
	{
		public override string ToString()
		{
			if (RefObj is OSMWay || RefObj is OSMNode)
				return RefObj.ToString();
			return "???";
		}
		public String Type;
		public Int64 Ref;
		public OSMObj RefObj;
		public String Role;
		public OSMMember(String _type, Int64 _ref, String role)
		{
			Type = _type;
			Ref = _ref;
			Role = role;
		}
	}

	public class OSMRelation : OSMObj
	{
		public OSMType Type = OSMType.Unknown;
		public OSMNode admin_centre = null;
		public Int64 _admin_centre = -1;
		public OSMRelation Parent;
		public OSMRelation[] Subareas = null;
		public override string ToString()
		{
			if (admin_centre != null)
				return "Boundary for " + (this["name"] == null ? "???" : this["name"]) + " (L" + (this["admin_level"] == null ? "?" : this["admin_level"]) + ")";
			return "Relation type: " + (Tags.ContainsKey("type") ? Tags["type"] : "unknown");
		}
		public List<OSMMember> Members = new List<OSMMember>();
		public OSMMember HasMember(Int64 ID)
		{
			for (int i = 0; i < Members.Count; i++)
				if (Members[i].Ref == ID)
					return Members[i];
			return null;
		}

		public OSMRelation(Int64 id)
			: base(id)
		{

		}

		public OSMMember AddMember(String _type, Int64 _ref, String role)
		{
			var res = HasMember(_ref);
			if (res != null)
				return res;

			OSMMember M = new OSMMember(_type, _ref, role);
			Members.Add(M);
			if (role == "admin_centre")
				Type = OSMType.AdminBorder;
			return M;
		}

		public List<OSMBorder> Borders = new List<OSMBorder>();

		public bool hasborder = false;
		public bool quickmade = false;

		void Debug()
		{
			Console.WriteLine("Debugging " + Tags["name"]);
			foreach (OSMMember M in Members)
				if (M.RefObj is OSMWay)
				{
					var w = (M.RefObj as OSMWay);
					Console.WriteLine(w.ID + "\t" + w.FirstNode.ID + "\t" + w.LastNode.ID);
				}
		}
		public bool MakeBorder()
		{
			Borders.Clear();
			hasborder = false;

			Borders.Clear();
			List<OSMWay> input = new List<OSMWay>();
			Members.FindAll(x => x.RefObj is OSMWay).ForEach(x => input.Add(x.RefObj as OSMWay));
			var osm = new osmperm(input);
			osm.ondepth += i =>
			{
			};
			osm.OnResult += l =>
			{
				var x = (l as osmperm).borders;
				for (int i = 0; i < x.Count; i++)
				{
					OSMBorder b = new OSMBorder();
					b.Ways.AddRange(x[i].Ways.ToArray());
					b.Dirs.AddRange(x[i].Dirs.ToArray());
					Borders.Add(b);
				}
				hasborder = true;
				quickmade = false;
				return false;
			};
			osm.StartSearch();
			return hasborder;
			/*
			int N = 0;
			int NS = 0;

			while (N < memberways.Count)
			{
				CurrentSeq = new List<OSMWay>();
				CurrentDir = new List<bool>();

				if (N == memberways.Count - 1)
				{
					hasborder = false;
					return false;
				}
				NS = N;
				int N2 = N + 1;
				OSMWay currentway = memberways[N];
				OSMWay nextway = memberways[N2];

				if (currentway.LastNode.ID == nextway.FirstNode.ID || currentway.LastNode.ID == nextway.LastNode.ID)
				{
					//Console.WriteLine("Start segment: normal");
					CurrentSeq.Add(currentway);
					CurrentDir.Add(false);
					N++;
				}
				else if (currentway.FirstNode.ID == nextway.FirstNode.ID || currentway.FirstNode.ID == nextway.LastNode.ID)
				{
					//Console.WriteLine("Start segment: reversed");
					CurrentSeq.Add(currentway);
					CurrentDir.Add(true);
					N++;
				}
				else
				{
					File.AppendAllLines("Error.txt", new string[] { ID.ToString() + " start error" });
					Console.WriteLine("Start error: #" + ID);
					return false;
				}
				bool closed = false;
				var StartPoint = CurrentDir[0] ? CurrentSeq[0].LastNode : CurrentSeq[0].FirstNode;
				while (!closed && N < memberways.Count + 1)
				{
					var EndPoint = CurrentDir[CurrentDir.Count - 1] ? CurrentSeq[CurrentSeq.Count - 1].FirstNode : CurrentSeq[CurrentSeq.Count - 1].LastNode;
					//close condition
					if (EndPoint.ID == StartPoint.ID)
					{
						closed = true;
					}
					else if (EndPoint.PosEq(StartPoint))
					{
						throw new Exception("Closed in wrong way");
					}
					else if (N < memberways.Count)
					{
						currentway = memberways[N];
						if (memberways[N].FirstNode.ID == EndPoint.ID)
						{
							CurrentSeq.Add(memberways[N]);
							CurrentDir.Add(false);
							N++;
						}
						else if (memberways[N].LastNode.ID == EndPoint.ID)
						{
							CurrentSeq.Add(memberways[N]);
							CurrentDir.Add(true);
							N++;
						}
						else if (EndPoint.PosEq(memberways[N].FirstNode) || EndPoint.PosEq(memberways[N].LastNode))
						{
							throw new Exception("Logical break");
						}
						else
						{
							Console.WriteLine("Line break for #" + ID + " (" + currentway.ID + "|" + currentway.LastNode.ID + ")");
							File.AppendAllLines("Error.txt", new string[] { ID.ToString() + " line break" });
							return false;
						}
					}
				}
				if (closed)
				{
					//Console.WriteLine("New segment: " + CurrentSeq.Count);
					Seq.Add(CurrentSeq);
					Seqdir.Add(CurrentDir);
				}
				//Console.WriteLine("End");
			}
			Border.MinMax();
			hasborder = true;
			return true;
			*/
		}
		public void MakeBorderR(String Path)
		{
			//Console.WriteLine(Path);
			if (MakeBorder())
			{
				for (int i = 0; i < Borders.Count; i++)
					Borders[i].MakePoly();
			}
			if (Subareas != null)
				foreach (var sub in Subareas)
					sub.MakeBorderR(Path + "->" + sub["name"]);
		}
		public OSMRelation PointInBorder(V3G P, bool recursive)
		{
			int NIN = 0;
			for (int i = 0; i < Borders.Count; i++)
			{
				if (Borders[i].BorderPoly.PointInPolygon(P))
					NIN++;
			}
			if (NIN % 2 == 0)
				return null;
			if (Subareas == null || Subareas.Length == 0 || !recursive)
				return this;
			OSMRelation res = null;
			for (int i = 0; i < Subareas.Length && res == null; i++)
				res = Subareas[i].PointInBorder(P, recursive);
			return res;
		}
		public OSMRelation PointInBorder2(V3G P, bool recursive)
		{
			//Console.WriteLine("Search in " + this["name"]);
			if (!hasborder)
				return null;
			int NIN = 0;
			for (int i = 0; i < Borders.Count; i++)
			{
				if (Borders[i].BorderPoly.PointInPolygon2(P))
					NIN++;
			}
			if (NIN % 2 == 0)
				return null;
			//Console.WriteLine("Passes: " + NIN);
			if (Subareas == null || Subareas.Length == 0 || !recursive)
				return this;
			OSMRelation res = null;
			for (int i = 0; i < Subareas.Length && res == null; i++)
				res = Subareas[i].PointInBorder2(P, recursive);
			return res;
		}
	}
}